package com.ybbase.framework.common.util;

import com.ybbase.framework.YbbaseApplication;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

/**
 * 通用反射工具类
 *
 * @author cg
 * @date 2019-09-19 14:39
 * @since 4.0
 */
@Component
public class ReflectionUtils {

    public static ReflectionUtils me;

    private ReflectionUtils(){
        //单例
    }

    //双重锁
    public static ReflectionUtils getInstance(){
        if (me==null) {
            synchronized (ReflectionUtils.class) {
                if(me == null){
                    me = new ReflectionUtils();
                }
            }
        }
        return me;
    }

    @Autowired
    WebApplicationContext applicationContext;

    /**
     * 获取类并启动类实例（调用不涉及注入的普通类）
     *
     * @param pathName pathName
     * @return Object 类对象
     * @author cg
     * @date 2019-09-19 14:41
     * @since 4.0
     */
    public static Object getObj(String pathName) {
        try {
            Class<?> cls = Class.forName(pathName);
            Object o = cls.newInstance();
            return o;
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据方法名调用指定对象的方法
     *
     * @param object     要调用方法的对象
     * @param methodName 要调用的方法名
     * @param args       参数对象数组
     * @return Object
     * @author cg
     * @date 2019-09-19 14:58
     * @since 4.0
     */
    public static Object invoke(Object object, String methodName, Object... args) {
        Object result = null;
        Class<? extends Object> clazz = object.getClass();
        Method queryMethod = getMethod(clazz, methodName, args);
        if (queryMethod != null) {
            try {
                result = queryMethod.invoke(object, args);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } else {
            try {
                throw new NoSuchMethodException(clazz.getName() + " 类中没有找到 " + methodName + " 方法。");
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 根据方法名和参数对象查找方法
     *
     * @param clazz      clazz
     * @param methodName 方法名
     * @param args       参数实例数据
     * @return Method
     * @author cg
     * @date 2019-09-19 14:58
     * @since 4.0
     */
    public static Method getMethod(Class<? extends Object> clazz, String methodName, Object[] args) {
        Method queryMethod = null;
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                // 判断传递的参数个数是否与方法参数的个数相同，可能存在重载的方法
                if (parameterTypes.length == args.length) {
                    boolean isSameMethod = true;
                    // 判断传递的参数类型是否与方法参数类型相同
                    for (int i = 0; i < parameterTypes.length; i++) {
                        Object arg = args[i];
                        if (arg == null) {
                            arg = "";
                        }
                        if (!parameterTypes[i].equals(args[i].getClass())) {
                            isSameMethod = false;
                        }
                    }
                    if (isSameMethod) {
                        queryMethod = method;
                        break;
                    }
                }
            }
        }
        return queryMethod;
    }

    /**
     * 根据javassist获取方法参数
     *
     * @param classPath  类路径
     * @param methodName 方法名
     * @return String[] 字符串数组
     * @author cg
     * @date 2019-10-29 14:43
     * @since 4.0
     */
    public static String[] getParamterName(String classPath, String methodName) {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.get(classPath);
            CtMethod cm = cc.getDeclaredMethod(methodName);
            // 使用javaassist的反射方法获取方法的参数名
            MethodInfo methodInfo = cm.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
            if (attr == null) {
                // exception
            }
            String[] paramNames = new String[cm.getParameterTypes().length];
            int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
            for (int i = 0; i < attr.tableLength(); i++) {
                if (attr.index(i) >= pos && attr.index(i) < paramNames.length + pos) {
                    paramNames[attr.index(i) - pos] = attr.variableName(i);
                }
            }
            /**
             String[] params = new String[cm.getParameterTypes().length+1];
             int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
             for (int i = 1; i < params.length; i++){
             params[i] = attr.variableName(i + pos);
             }*/
            return paramNames;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取类并启动类实例（调用涉及注入的类）
     *
     * @param pathName 路径名
     * @return Object
     * @author cg
     * @date 2019-09-19 14:58
     * @since 4.0
     */
    public Object getAutoObj(String pathName) {
        try {
            Class<?> cls = Class.forName(pathName);
            Object o = YbbaseApplication.context.getBean(cls);
            return o;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 通过Java8的反射方式获取方法的参数名称列表
     *
     * @param clazz      clazz
     * @param methodName 方法名
     * @return List<String>
     * @author cg
     * @date 2019-10-24 16:05
     * @since 4.0
     */
    public List<String> getParameterNameJava8(Class<? extends Object> clazz, @Param(value = "") String methodName) {
        List<String> paramterList = new ArrayList<>();
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if (methodName.equals(method.getName())) {
                /**
                 * 返回的就是方法中的参数名列表了
                 * */
                Parameter[] params = method.getParameters();
                for (Parameter parameter : params) {
                    paramterList.add(parameter.getName());
                }

            }
        }
        return paramterList;
    }


}